/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.sys>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: new_file.cpp
 *
 * Purpose: new file
 *
 * Developer:
 *   wen.gu , 2020-07-29
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "type_parser.h"

#include <sstream>
#include <stdint.h>

#include "utils.h"
#include "key_word.h"

#include "log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/



/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/



/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/


static inline bool IsTypeInteger(DataType type)
{
    return (DataType::kInt8 == type)||(DataType::kInt16 == type) || (DataType::kInt32 == type)||(DataType::kInt64 == type) ||
           (DataType::kUInt8 == type)||(DataType::kUInt16 == type)||(DataType::kUInt32 == type)||(DataType::kUInt64 == type);
}

#if 0
static inline bool IsTypeBool(const std::string& type)
{
    return TYPE_BOOL == type;
}
#endif




#if 0
static bool checkValueIsNumber(const string& str)  
{  
    const int valSize
    for (int i = 0; i < str.size(); i++)
    {
        int tmp = (int)str[i];
        if (tmp >= 48 && tmp <= 57)
        {
            continue;
        }
        else
        {
            return false;
        }
    } 
    return true;
}  

bool IsBaseType(DataType type)
{

}
#endif
#if 0
bool CheckTypeValid(DataTypeArray& root, const std::string& type)
{
    if (Str2BaseType(type) != DataType::kUnknown)
    { /** the type is base type */
        return true;
    }


    for (uint32_t i =0; i < root.size(); i++)
    {/** the type is already parsed composition type */
        DataTypeInfo& dti = root[i];
        if (dti.name == type)
        {
            return true;
        }
    }

    return false;
}

bool CheckElementType(DataTypeArray& root, const std::string& type)
{
    DataType dt = Str2BaseType(type);
    if (dt != DataType::kUnknown)
    { /** the type is base type */
        return true;
    }


    for (uint32_t i =0; i < root.size(); i++)
    {/** the type is already parsed composition type */
        DataTypeInfo& dti = root[i];
        if (dti.name == type)
        {
            return true;
        }
    }

    return false;    
}
#endif

static bool ParseEnumElementType(DataTypeArray& root, const std::string& element_type, DataType& t)
{
    DataType dt = Str2BaseType(element_type);
    if (dt == DataType::kUnknown)
    {
        return false;
    }

    if (IsTypeInteger(dt))
    {
        t = dt;
        return true;
    }

    return false;
}

static bool CheckEnumElementValue(const YAML::Node& nd, DataType element_type)
{

    try
    {
        uint64_t val = nd.as<uint64_t>();

        switch(element_type)
        {
        case DataType::kInt8: 
        {
            int64_t ival = (int64_t)val;
            if ((INT8_MIN <= ival) && (ival <= (int64_t)INT8_MAX))
            {
                return true;
            }
        }        
        break;
        case DataType::kInt16: 
        {
            int64_t ival = (int64_t)val;
            if ((INT16_MIN <= ival) && (ival <= (int64_t)INT16_MAX))
            {
                return true;
            }
        }
        break;
        case DataType::kInt32: 
        {
            int64_t ival = (int64_t)val;
            if ((INT32_MIN <= ival) && (ival <= (int64_t)INT32_MAX))
            {
                return true;
            }
        }        
        break;
        case DataType::kInt64: 
        {
            //if ()
            return true; /** todo, refine me?? */
        }        
        break;
        case DataType::kUInt8: 
        {
            uint64_t ival = (uint64_t)val;
            if (((uint64_t)0 <= ival) && (ival <= (uint64_t)UINT8_MAX))
            {
                return true;
            }
        }        
        break;
        case DataType::kUInt16: 
        {
            uint64_t ival = (uint64_t)val;
            if (((uint64_t)0 <= ival) && (ival <= (uint64_t)UINT16_MAX))
            {
                return true;
            }
        }            
        break;
        case DataType::kUInt32: 
        {
            uint64_t ival = (uint64_t)val;
            if (((uint64_t)0 <= ival) && (ival <= (uint64_t)UINT32_MAX))
            {
                return true;
            }
        }            
        break;
        case DataType::kUInt64: 
        {
            return true; /** todo, refine me */
        }
        break;
        default: return false; break;    
        }
    }
    catch(const YAML::BadConversion & e)
    {
        return false;
    }

    return false;
}

bool ParseTypeStruct(const std::string& name, const YAML::Node& nd, DataTypeArray& dta)
{
    LOGD("[DBG][%s.%d]:===>:struct %s\n", __FUNCTION__, __LINE__, name.c_str());
    DataTypeInfo dti;
    dti.type = DataType::kStruct;
    dti.name = name;
    DataTypeElements& dte = dti.elements;

    if (!HasMember(nd, KW_ELEMENT))
    {
        PrintTypeError(nd, "struct", name, "struct type must have " KW_ELEMENT " field");
        return false;
    }

    YAML::Node elements = nd[KW_ELEMENT];
    if (!ParseDataElementArray(dta, dte, nd[KW_ELEMENT]))
    {
        LOGE("parse data elements of struct failed\n");
        return false;
    }
    
    if (dte.size() == 0)
    {
        PrintTypeError(nd, "struct", name, "hanven't any element");
        return false;
    }

    ParseComment(nd, dti.comment);
    dta.push_back(std::move(dti));
    return true;

}

bool ParseTypeVector(const std::string& name, const YAML::Node& nd, DataTypeArray& dta)
{
    LOGD("[DBG][%s.%d]:===>: parse vector %s\n", __FUNCTION__, __LINE__, name.c_str());
    DataTypeInfo dti;
    dti.type = DataType::kVector;
    dti.name = name;

    if (!HasMember(nd, KW_ELEMENT_TYPE))
    {
        PrintTypeError(nd, "vector", name, "vector type must have " KW_ELEMENT_TYPE " field");
        return false;
    }

    std::string element_type = nd[KW_ELEMENT_TYPE].as<std::string>();
    
    if (HasTypeDef(dta, element_type))
    {
        dti.element_type = element_type;
    }
    else
    {
        PrintTypeError(nd,"vector::_element_type : ", element_type, "not declare before current line");
        return false;
    }  

    ParseComment(nd, dti.comment);
    dta.push_back(std::move(dti));
    return true;
}

bool ParseTypeEnum(const std::string& name, const YAML::Node& nd, DataTypeArray& dta)
{
    LOGD("[DBG][%s.%d]:===>:enum %s\n", __FUNCTION__, __LINE__, name.c_str());
    DataTypeInfo dti;
    dti.type = DataType::kEnum;
    dti.name = name;
    DataTypeElements& dte = dti.elements;
    DataType ele_type = DataType::kInt32; /** default enum element type int */
    
    if (HasMember(nd, KW_ELEMENT_TYPE))
    {
        YAML::Node nd_element_type = nd[KW_ELEMENT_TYPE];
        if (!nd_element_type.IsScalar())
        {
            PrintTypeError(nd,"enum::_element_type", "", "must be a scalar");
            return false;
        }
        std::string element_type_str = nd_element_type.as<std::string>();
        if (ParseEnumElementType(dta, element_type_str, ele_type))
        {
            dti.element_type = element_type_str;
        }
        else
        {
            PrintTypeError(nd,"enum::_element_type : ", element_type_str, "enum element type must be integer");
            return false;
        }  
    }

    if (!HasMember(nd, KW_ELEMENT))
    {
        PrintTypeError(nd, "enum", name, "enum type must have " KW_ELEMENT " field");
        return false;
    }

    YAML::Node elements = nd[KW_ELEMENT];
    for (YAML::Node::iterator it = elements.begin(); it != elements.end(); ++it)
    {
        if (CheckEnumElementValue(it->second, ele_type)) /** the enum value must be integer */
        {
            DataTypeElement et;
            et.type = it->first.as<std::string>(); /** the enum define, */
            et.name = it->second.as<std::string>(); /** the value for enum element, */
            dte.push_back(et);
        }
        else
        {
            PrintTypeError(it->first, it->first.as<std::string>(), it->second.as<std::string>(), "the element value must be integer");
            return false;
        }
    }

    if (dte.size() == 0)
    {
        PrintTypeError(nd, "enum", name, "hanven't any element");
        return false;
    }

    ParseComment(nd, dti.comment);
    dta.push_back(std::move(dti));
    return true;
}

bool ParseTypeMap(const std::string& name, const YAML::Node& nd, DataTypeArray& dta)
{
    LOGD("[DBG][%s.%d]:===>:map %s\n", __FUNCTION__, __LINE__, name.c_str());
    DataTypeInfo dti;
    dti.type = DataType::kMap;
    dti.name = name;

    if (!HasMember(nd, KW_KEY))
    {
        PrintTypeError(nd, "map", name, "map type must have " KW_KEY " field");
        return false;
    }

    if (!HasMember(nd, KW_VALUE))
    {
        PrintTypeError(nd, "map", name, "map type must have " KW_VALUE " field");
        return false;
    }

    DataTypeElement et;/**todo, refine me ?? */
    et.type = nd[KW_KEY].as<std::string>();
    if (!HasTypeDef(dta, et.type))  /**DataTypeElement::type as the key type of map */
    {
        PrintTypeError(nd, "map::_key: ", et.type, "invalid type for " KW_KEY " field");
        return false;
    }

    et.name = nd[KW_VALUE].as<std::string>();

    if (!HasTypeDef(dta, et.name))  /** DataTypeElement::name as the value type of map */
    {
        PrintTypeError(nd, "map::_value: ", et.name, "invalid type for " KW_VALUE " field");
        return false;
    }

    dti.elements.push_back(std::move(et));
    ParseComment(nd, dti.comment);
    dta.push_back(std::move(dti));

    return true;
}

bool ParseTypeArray(const std::string& name, const YAML::Node& nd, DataTypeArray& dta)
{
    DataTypeInfo dti;
    dti.type = DataType::kArray;
    dti.name = name;

    if (!HasMember(nd, KW_ELEMENT_TYPE))
    {
        PrintTypeError(nd, "array", name, "array type must have " KW_ELEMENT_TYPE " field");
        return false;
    }

    std::string size_str;

    if (HasMember(nd, KW_SIZE))
    {
        YAML::Node nd_size = nd[KW_SIZE];
        if (nd_size.IsScalar())
        {
            size_str = nd_size.as<std::string>(); 
        }
         
        if (!CheckEnumElementValue(nd_size, DataType::kUInt64)) /** todo, refine me?? */
        {
            PrintTypeError(nd,"array::_size", size_str, "the _size field of array muste be integer");
            return false;
        }
    }
    else
    {
        dti.type = DataType::kVector;
        PrintWarning(nd, "haven't set _size field, so default convert to the type of vector");
    }

    std::string element_type = nd[KW_ELEMENT_TYPE].as<std::string>();

    if (HasTypeDef(dta, element_type))
    {
        dti.element_type = element_type;
        DataTypeElement et;/**todo, refine me ?? */
        et.type = size_str;  /**DataTypeElement::type as the size of array */
        dti.elements.push_back(std::move(et));
    }
    else
    {
        PrintTypeError(nd,"array::_element_type : ", element_type, "not declare before current line");
        return false;
    }  
    ParseComment(nd, dti.comment);
    dta.push_back(std::move(dti));
    return true;
}




/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
TypeParser::TypeParser()
    :ContentParser(ContentType::kTypeDef)
{
    /** todo, something */    
}

TypeParser::~TypeParser()
{
    /** todo, something */
}



bool TypeParser::parse(const YAML::Node& typedefs, FileContent& file_content)
{
    if (!typedefs.IsMap())
    {
        return false;
    }

    for (YAML::Node::const_iterator it = typedefs.begin(); it != typedefs.end(); ++it )
    {
        const YAML::Node& type = it->second;
        if (!type.IsMap())
        {       
            PrintError(type, "a data type define must be a object node of sidl");
            return false;
        } 

        if (HasMember(type, KW_TYPE))
        {
            std::string type_name = it->first.as<std::string>(); 
            std::string type_str = type[KW_TYPE].as<std::string>();
            DataType dt = Str2CompositeType(type_str);
            if (dt != DataType::kUnknown)
            {
                bool ret = false;
                switch(dt)
                {
                case DataType::kStruct: ret = ParseTypeStruct(type_name, type, file_content.type_defs); break;
                case DataType::kVector: ret = ParseTypeVector(type_name, type, file_content.type_defs); break;
                case DataType::kEnum:   ret = ParseTypeEnum(type_name, type, file_content.type_defs); break;
                case DataType::kArray:  ret = ParseTypeArray(type_name, type, file_content.type_defs); break;
                case DataType::kMap:    ret = ParseTypeMap(type_name, type, file_content.type_defs); break;
                default:
                    PrintTypeError(type, type_str, type_name, "unsupported composite type");
                    return false;
                    break;
                }

                if (!ret)
                {
                    PrintTypeError(type, type_str, type_name, "parse failed");
                    return false;
                }
            }
            else
            {
                PrintError(type, "need composite type at here");
                return false;
            }
        }
    }

    return true; 
}






